﻿using System;
using System.Collections.Specialized;
using System.Reflection;
using System.Web.Mvc;

namespace WB.Mvc.Forms
{
	public class ModelFormBinder : DefaultModelBinder
	{
		public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
		{
			bindingContext.ModelType = GetModelType(bindingContext);

			object model = bindingContext.Model ?? CreateModel(controllerContext, bindingContext, bindingContext.ModelType);

			if (!(model is FormBase))
				throw new InvalidOperationException(string.Format("Expected a model of type FormBase."));

			bool isTransient = !bindingContext.ValueProvider.ContainsKey(ModelForm.primaryKeyInputId);

			var modelForm = (model as ModelForm);
			if (isTransient)
			{
				bool hasEmptyConstructor = modelForm.ModelType != null &&
				                           modelForm.ModelType.GetConstructor(Type.EmptyTypes) != null;
	
				if (hasEmptyConstructor)
				{
					modelForm.Model = Activator.CreateInstance(modelForm.ModelType);

					var formValues = new NameValueCollection();
					foreach (var kvp in bindingContext.ValueProvider)
					{
						formValues.Add(kvp.Key, kvp.Value.AttemptedValue);
					}

					modelForm.Load(formValues);
				}
			}
			else
			{
				modelForm.UniqueIdentifier = bindingContext.ValueProvider[ModelForm.primaryKeyInputId].AttemptedValue;
			}

			return model;
		}

		protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext,
		                                      Type modelType)
		{
			if (!(typeof (ModelForm).IsAssignableFrom(modelType)))
			{
				throw new InvalidOperationException("Not a model form");
			}

			ConstructorInfo emptyConstructor = modelType.GetConstructor(Type.EmptyTypes);
			if (emptyConstructor == null)
			{
				throw new InvalidOperationException(
					string.Format(
						"Could not find an empty constructor for type {0}.  If no empty constructor is available, please create a model binder that inherits from {1} and override CreateModel.",
						modelType.FullName, GetType().FullName));
			}

			var modelForm = Activator.CreateInstance(modelType);
			return modelForm;
		}

		/// <summary>
		/// Retrieve the model type expected.
		/// </summary>
		protected virtual Type GetModelType(ModelBindingContext bindingContext)
		{
			if (!bindingContext.ValueProvider.ContainsKey(ModelForm.typeInputId))
				throw new InvalidOperationException("Could not find type.");

			return Type.GetType(bindingContext.ValueProvider[ModelForm.typeInputId].AttemptedValue);
		}
	}
}